package com.canyou.utils;

import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.canyou.constants.CommonConstants;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.io.FilenameUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.util.zip.*;

/**
 * 文件操作帮助类
 *
 * @author fy
 * @date 20/03/09 16:14
 */
public final class FileUtils {

    private FileUtils() {
    }

    private static final int BUFFER = 8192;

    public static void compress(String srcPath, String dstPath) throws IOException {
        File srcFile = new File(srcPath);
        File dstFile = new File(dstPath);
        if (!srcFile.exists()) {
            throw new FileNotFoundException(srcPath + "不存在！");
        }

        try (FileOutputStream out = new FileOutputStream(dstFile);
             CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
             ZipOutputStream zipOut = new ZipOutputStream(cos)) {

            String baseDir = "";
            compress(srcFile, zipOut, baseDir);
        }
    }

    private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
        if (file.isDirectory()) {
            compressDirectory(file, zipOut, baseDir);
        } else {
            compressFile(file, zipOut, baseDir);
        }
    }

    /**
     * 压缩一个目录
     *
     * @param dir
     * @param zipOut
     * @param baseDir
     * @throws IOException
     */
    private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir) throws IOException {
        File[] files = dir.listFiles();
        for (int i = 0; i < files.length; i++) {
            compress(files[i], zipOut, baseDir + dir.getName() + "/");
        }
    }

    /**
     * 压缩一个文件
     *
     * @param file
     * @param zipOut
     * @param baseDir
     * @throws IOException
     */
    private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
        if (!file.exists()) {
            return;
        }

        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            ZipEntry entry = new ZipEntry(baseDir + file.getName());
            zipOut.putNextEntry(entry);
            int count;
            byte[] data = new byte[BUFFER];
            while ((count = bis.read(data, 0, BUFFER)) != -1) {
                zipOut.write(data, 0, count);
            }
        }
    }

    /**
     * 解压缩文件到指定目录
     *
     * @param zipFile
     * @param dstPath
     * @throws IOException
     */
    public static void decompress(String zipFile, String dstPath) throws IOException {
        File pathFile = new File(dstPath);
        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }
        try (ZipFile zip = new ZipFile(zipFile)) {
            for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String zipEntryName = entry.getName();

                try (InputStream in = zip.getInputStream(entry)) {
                    String outPath = (dstPath + "/" + zipEntryName).replaceAll("\\*", "/");
                    // 判断路径是否存在,不存在则创建文件路径
                    File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    // 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
                    if (new File(outPath).isDirectory()) {
                        continue;
                    }

                    try (OutputStream out = new FileOutputStream(outPath)) {
                        byte[] buf1 = new byte[1024];
                        int len;
                        while ((len = in.read(buf1)) > 0) {
                            out.write(buf1, 0, len);
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        String targetFolderPath = "/Users/root/zipFile/zipFolder";
        String rawZipFilePath = "/Users/root/zipFile/raw.zip";
        String newZipFilePath = "/Users/root/zipFile/new.zip";

        // 将Zip文件解压缩到目标目录
        decompress(rawZipFilePath, targetFolderPath);

        // 将目标目录的文件压缩成Zip文件
        compress(targetFolderPath, newZipFilePath);
    }

    public enum FileType {
        // 未知
        UNKNOWN,
        // 压缩文件
        ZIP, RAR, _7Z, TAR, GZ, TAR_GZ, BZ2, TAR_BZ2,
        // 位图文件
        BMP, PNG, JPG, JPEG,
        // 矢量图文件
        SVG,
        // 影音文件
        AVI, MP4, MP3, AAR, OGG, WAV, WAVE
    }

    /**
     * 获取文件真实类型
     *
     * @param file 要获取类型的文件。
     * @return 文件类型枚举。
     */
    private static FileType getFileType(File file) throws IOException {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            byte[] head = new byte[4];
            if (-1 == inputStream.read(head)) {
                return FileType.UNKNOWN;
            }
            int headHex = 0;
            for (byte b : head) {
                headHex = ((headHex << 8) | (b & 0xff));
            }
            switch (headHex) {
                case 0x504B0304:
                    return FileType.ZIP;
                case 0x776f7264:
                    return FileType.TAR;
                case -0x51:
                    return FileType._7Z;
                case 0x425a6839:
                    return FileType.BZ2;
                case -0x74f7f8:
                    return FileType.GZ;
                case 0x52617221:
                    return FileType.RAR;
                default:
                    return FileType.UNKNOWN;
            }
        }
    }

    /**
     * 构建目录
     *
     * @param outputDir 输出目录
     * @param subDir    子目录
     */
    private static void createDirectory(String outputDir, String subDir) {
        File file = new File(outputDir);
        // 子目录不为空
        if (StringUtils.isNotBlank(subDir)) {
            file = new File(outputDir + File.separator + subDir);
        }
        if (!file.exists()) {
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            file.mkdirs();
        }
    }

    /**
     * 解压缩bz2文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static void decompressBz2(File file, String targetPath) throws IOException {
        String suffix = ".bz2";
        try (FileInputStream fis = new FileInputStream(file)) {
            try (BZip2CompressorInputStream bis = new BZip2CompressorInputStream(fis)) {
                // 创建输出目录
                createDirectory(targetPath, null);
                File tempFile = new File(targetPath + File.separator + file.getName().replace(suffix, ""));
                try (OutputStream fos = new FileOutputStream(tempFile)) {

                    int count;
                    byte[] data = new byte[2048];
                    while ((count = bis.read(data)) != -1) {
                        fos.write(data, 0, count);
                    }
                    fos.flush();
                }
            }
        }
    }

    /**
     * 解压缩gz文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    private static void decompressGz(File file, String targetPath) throws IOException {
        String suffix = ".gz";
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            try (GZIPInputStream gzipIn = new GZIPInputStream(fileInputStream)) {
                // 创建输出目录
                createDirectory(targetPath, null);

                File tempFile = new File(targetPath + File.separator + file.getName().replace(suffix, ""));
                try (OutputStream out = new FileOutputStream(tempFile)) {
                    int count;
                    byte[] data = new byte[2048];
                    while ((count = gzipIn.read(data)) != -1) {
                        out.write(data, 0, count);
                    }
                    out.flush();
                }
            }
        }
    }

    /**
     * 解压缩7z文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    private static void decompress7z(File file, String targetPath) throws IOException {
        try (SevenZFile sevenzFile = new SevenZFile(file)) {
            // 创建输出目录
            createDirectory(targetPath, null);
            SevenZArchiveEntry entry;

            while ((entry = sevenzFile.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    // 创建子目录
                    createDirectory(targetPath, entry.getName());
                } else {
                    try (OutputStream outputStream = new FileOutputStream(new File(targetPath + File.separator + entry.getName()))) {
                        int len;
                        byte[] b = new byte[2048];
                        while ((len = sevenzFile.read(b)) != -1) {
                            outputStream.write(b, 0, len);
                        }
                        outputStream.flush();
                    }
                }
            }
        }
    }

    /**
     * 拼接文件名和后缀
     *
     * @param name 文件名
     * @param ext  后缀
     * @return
     */
    public static String concatFilename(String name, String ext) {
        final String point = ".";
        String fileExtName = FilenameUtils.getExtension(name);
        // 如果原名字已经是一个完整的文件名
        if (StringUtils.isNotBlank(fileExtName)) {
            return name;
        }
        // 如果后缀已经存在 `.`
        if (ext.indexOf(point) == 0) {
            return name + ext;
        }
        return name + point + ext;
    }

    /**
     * 根据文件扩展名, 获取mime_type, 如果extName为空, 或者获取不到对应的mime类型, 则返回null
     *
     * @param extName 文件扩展名
     * @return mime_type Entry
     */
    public static Map.Entry<String, String[]> getMimeTypeEntry(String extName) {
        return CommonConstants.getMimeType()
                .entrySet()
                .stream()
                .filter(entry -> entry.getValue()[0].equalsIgnoreCase(extName))
                .findFirst()
                .orElse(null);
    }

    /**
     * 计算文件的 sha256 hash code
     *
     * @param file
     * @return
     */
    public static String checkSha256(File file) {
        String hashCode = "";
        try {
            // 计算文件sha256 hash值
            hashCode = Files.asByteSource(file).hash(Hashing.sha256()).toString();
        } catch (IOException e) {
            // ignore
        }
        return hashCode;
    }

    /**
     * 解压zip文件到临时目录, 返回解压后的文件夹路径
     *
     * @param filePath zip文件路径
     * @return
     */
    public static String unzipFile(String filePath) {
        // 先检测文件是否存在, 不存在则直接返回
        if (!FileUtil.exist(filePath)) {
            throw new RuntimeException("zip压缩文件不存在");
        }
        // 获取一个临时目录
        String tmpPath = PathUtils.getTmpPath();
        // 解压后的目录
        String unZipDir;
        try {
            // 压缩路径存在中文, 但是在windows上又使用GBK编码时, 就会导致解压失败
            unZipDir = ZipUtil.unzip(filePath, tmpPath, Charset.forName("GBK")).getAbsolutePath();
        } catch (UtilException e1) {
            try {
                unZipDir = ZipUtil.unzip(filePath, tmpPath).getAbsolutePath();
            } catch (UtilException e) {
                throw new RuntimeException("附件zip压缩文件解压失败", e);
            }
        }
        return unZipDir;
    }

    /**
     * 清理目录中过期的文件
     *
     * @param dirPath 文件路径
     * @param hour    时间 /小时
     */
    public static void deleteTimeout(String dirPath, int hour) throws IOException {
        Date date = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * hour);
        File folder = new File(dirPath);
        File[] files = folder.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            if (new Date(file.lastModified()).before(date)) {
                java.nio.file.Files.delete(Paths.get(file.getPath()));
            }
        }
    }

}
